//
//  OtaTest.swift
//  BxToolsApp
//
//

import Foundation
import CoreBluetooth
import SwiftUI

@objc public protocol OtaDelegate : NSObjectProtocol {
    
    @objc func onInitialError(error errorMessage:String)
    
    @objc func onTaskStateChanged(state:OtaState)
    
    @objc func onFinish()
    
}

public enum TransferState : Int {
    case idle = 0
    case requestStart1 = 1
    case requestStart2 = 2
    case block = 3
    case segment = 4
    case end = 5
    case apply = 6
    case finish = 7
    case error = -1
}

@objc
public class OtaState: NSObject {
    @objc
    public let index:Int
    @objc
    public var startTime: TimeInterval = 0
    @objc
    public var endTime: TimeInterval = 0
    @objc
    public var progress:Float = 0
    @objc
    public var result:Bool = false
    @objc
    public var errorMessage:String? = nil
    
    public init(index: Int) {
        self.index = index
    }
}

public typealias StateCallback = (OtaState) -> Void

class BasicProcess : NSObject, CBPeripheralDelegate{
    
    private var target:CBPeripheral!
    private var _ctrl: CBCharacteristic!
    private var _data: CBCharacteristic!
    private var task:BxOtaTask!
    private var fastMode = false
    private var state = TransferState.idle
    private var lastState = TransferState.idle
    private var callback:StateCallback?
    private var index:Int!
    private var otaState:OtaState!
    
    private var dispatchWorkItem:DispatchWorkItem? = nil
    
    init(peripheral target:CBPeripheral, ctrl: CBCharacteristic, data: CBCharacteristic, index:Int, task:BxOtaTask, fastMode:Bool){
        super.init()
        self.target = target
        self._ctrl = ctrl
        self._data = data
        self.task = task
        self.index = index
        self.fastMode = fastMode
        self.otaState = OtaState(index: index)
        self.otaState.startTime = Date().timeIntervalSince1970
        target.delegate = self
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil{
            print(error!)
        }else{
            print(characteristic)
        }
        if characteristic.uuid != _ctrl.uuid {
            return
        }
        if error != nil {
            postError(error: "Indicate Ctrl Command Failed!")
            return
        }
        guard let value = characteristic.value else {
            return
        }
        if(value[0] == 1){
            cancelTimeout()
            if(value[1] == 0){
                var mtuSize = 20
                if(fastMode){
                    mtuSize = target.maximumWriteValueLength(for: .withoutResponse)
                }
                print("mtu: \(mtuSize)")
                task.setup(maxSegmentNumInBlock: Int(value[2]) * 8, mtuSize: mtuSize)
                //开始请求第一个Block
                requestNextBlock()
            }else{
                //todo error reject
                postError(error: "Remote Device reject Ota")
            }
        }
    }
    
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if(characteristic.uuid == _ctrl.uuid){
            //1. 开始 ignore
            //2. block
            //3. end ignore
            switch self.state{
            case .requestStart1:
                if(error != nil){
                    self.requestStartOta2()
                }
                break
            case .requestStart2:
                if(error != nil){
                    postError(error: "Write Ctrl Command Failed!")
                }
                break
            case .end:
                print(String(describing: error))
                //这里忽略错误，兼容嵌入式软件的版本- -!
                if(fastMode){
                    apply()
                }else{
                    finish()
                }
                break
            case .apply:
                if(error != nil){
                    postError(error: "Apply Failed")
                }else{
                    finish()
                }
                break
            case .block:
                if error != nil{
                    self.retryBlock(err: String(describing: error))
                }else{
                    cancelTimeout()
                    postCurrentState(newState: .segment)
                    transferSegment()
                }
                break
            default :
                break
            }
        }
    }
    
    func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral){
        transferSegment()
    }
    
    func start(_ callback : @escaping StateCallback){
        print(">>>>>>>>>> ota manager start")
        cancelTimeout()
        dispatchWorkItem = DispatchWorkItem{
            self.postError(error: "Start OTA failed because there is no response")
        }
        DispatchQueue.main.asyncAfter(deadline: .now()+5, execute: dispatchWorkItem!)
        self.lastState = .idle
        self.state = .idle
        self.callback = callback
        requestStartOta()
    }
    
    private func requestStartOta(){
        guard let cmd = task.startPkt(true) else{
            self.state = .error
            postError(error: "Load 'requestStart' Command Failed")
            return
        }
        postCurrentState(newState: .requestStart1)
        print(cmd.hexadecimal())
        target.writeValue(cmd, for: _ctrl,type: .withResponse)
    }
    
    private func requestStartOta2(){
        guard let cmd = task.startPkt(false) else{
            self.state = .error
            postError(error: "Load 'requestStart' Command Failed")
            return
        }
        postCurrentState(newState: .requestStart2)
        print(cmd.hexadecimal())
        target.writeValue(cmd, for: _ctrl,type: .withResponse)
    }
    
    private var retryBlockCommand:Data? = nil
    private var retryBlockTime = 0
    
    private func requestNextBlock(){
        guard let cmd = task.loadNextBlockPacket() else{
            transferEnd()
            return
        }
        retryBlockCommand = cmd
        retryBlockTime = 0
        cancelTimeout()
        dispatchWorkItem = DispatchWorkItem{
            self.retryBlock(err: "Request Block Timeout")
//            self.postError(error: "New Block Timeout")
        }
        DispatchQueue.main.asyncAfter(deadline: .now()+3, execute: dispatchWorkItem!)
        postCurrentState(newState: .block)
        target.writeValue(cmd, for: _ctrl,type: .withResponse)
    }
    
    private func retryBlock(err: String?){
        cancelTimeout()
        guard let cmd = retryBlockCommand else{
            postError(error: err ?? "Unknown Error")
            return
        }
        retryBlockTime += 1
        if retryBlockTime < 2{
            dispatchWorkItem = DispatchWorkItem{
                self.retryBlock(err: "Request Block Timeout")
            }
            DispatchQueue.main.asyncAfter(deadline: .now()+3, execute: dispatchWorkItem!)
            postCurrentState(newState: .block)
            target.writeValue(cmd, for: _ctrl,type: .withResponse)
        }else{
            postError(error: err ?? "Unknown Error")
        }
    }
    
    private func transferSegment(){
        guard let segmentData = task.loadNextSegment() else{
            requestNextBlock()
            return
        }
        target.writeValue(segmentData, for: _data,type: .withoutResponse)
    }
    
    private func transferEnd(){
        var data = Data.init()
        data.append(contentsOf: [3.toUInt8()])
        let endCmd = data
        postCurrentState(newState: .end)
        target.writeValue(endCmd, for: _ctrl,type: .withResponse)
        print("\(endCmd.hexadecimal())")
        if(!fastMode){
            finish()
        }else{
            //wait response
        }
    }
    
    private let REQUEST_CTRL_APPLY = 0x0A
    private let REQUEST_CTRL_REBOOT = 0x0B
    private func apply(){
        var cmd = Data()
        cmd.append(UInt8(REQUEST_CTRL_APPLY))//1
        postCurrentState(newState: .apply)
        self.target?.writeValue(cmd, for: self._ctrl!,type: .withResponse)
    }
    
    private func finish(){
        self.otaState.result = true
        self.lastState = self.state
        self.state = .finish
        self.otaState.endTime = Date().timeIntervalSince1970
        self.otaState.progress = currentProgress()
        self.callback?(self.otaState)
    }
    
    private func postError(error:String){
        self.lastState = self.state
        self.state = .error
        self.otaState.endTime = Date().timeIntervalSince1970
        self.otaState.progress = currentProgress()
        self.otaState.errorMessage = error
        self.otaState.result = false
        self.callback?(self.otaState)
    }

    private func postCurrentState(newState: TransferState){
        self.lastState = self.state
        self.state = newState
        self.otaState.progress = currentProgress()
        self.callback?(self.otaState)
    }
    
    private func currentProgress() -> Float {
        if task.allBlockSize == 0 {return 0}
        let remain = Float.init( task.blocks.count )
        let all = Float.init( task.allBlockSize )
        return 1 - remain / all
    }
    
    private func cancelTimeout(){
        if !(dispatchWorkItem?.isCancelled ?? false) {
            dispatchWorkItem?.cancel()
        }
    }
}
